using System.Linq;
using FluentNHibernate.Mapping;
using NHibernate;
using NHibernate.Linq;
using NHibernateInsight.Tests.Domain;
using NHibernateInsight.Tests.Infrastructure;
using NUnit.Framework;
using NHibernateInsight.Tests.Insights.Linq.Extensions;

namespace NHibernateInsight.Tests.Insights.Linq
{
    [TestFixture]
    public class Expanding_a_Linq_query : TestContext
    {
        private class Mappings_of
        {
            public class Product : ClassMap<Domain.Product>
            {
                public Product()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.Prospects)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsBag();
                }
            }
            public class Prospect : ClassMap<Domain.Prospect>
            {
                public Prospect()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    Map(x => x.Initials);
                    HasManyToMany(x => x.Products)
                        .Cascade.SaveUpdate()
                        .AsBag();
                    HasManyToMany(x => x.ProductsSet)
                        .Cascade.SaveUpdate()
                        .Table("ProductsToProspects")
                        .AsSet();
                    HasMany(x => x.Orders)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsBag();
                    HasMany(x => x.OrdersSet)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsSet();
                    HasMany(x => x.Addresses)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsBag();
                    HasMany(x => x.AddressesSet)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsSet();
                }
            }
            public class Order : ClassMap<Domain.Order>
            {
                public Order()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    References(x => x.Prospect)
                        .Cascade.SaveUpdate();
                }
            }
            public class Address : ClassMap<Domain.Address>
            {
                public Address()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    References(x => x.Prospect)
                        .Cascade.SaveUpdate();
                }
            }

        }

        [SetUp]
        public void Given_that()
        {
            PrepareForSql2005ExpressDb();


            HideCreateAndDropDbSqlFromConsole();
            HideCreateAndDropSchemaSqlFromConsole();

            // Prepare data!
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect>()
                          .Add<Mappings_of.Order>()
                          .Add<Mappings_of.Address>());

            var product1 = new Product();
            var product2 = new Product();
            var product3 = new Product();
            var order1 = new Order();
            var order2 = new Order();
            var order3 = new Order();
            var order4 = new Order();
            var order5 = new Order();
            var address1 = new Address();
            var address2 = new Address();
            var prospect = new Prospect() { Initials = "Initials" };

            prospect.RelateTo(product1);
            prospect.RelateTo(product2);
            prospect.RelateTo(product3);
            prospect.RelateTo(order1);
            prospect.RelateTo(order2);
            prospect.RelateTo(order3);
            prospect.RelateTo(order4);
            prospect.RelateTo(order5);
            prospect.RelateTo(address1);
            prospect.RelateTo(address2);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }

            CurrentSession.Evict(product1);
            CurrentSession.Evict(product2);
            CurrentSession.Evict(product3);
            CurrentSession.Evict(order1);
            CurrentSession.Evict(order2);
            CurrentSession.Evict(order3);
            CurrentSession.Evict(order4);
            CurrentSession.Evict(order5);
            CurrentSession.Evict(address1);
            CurrentSession.Evict(address2);
            CurrentSession.Evict(prospect);
        }

        [TearDown]
        public void Cleanup_database()
        {
            Cleanup();
        }

        /// <summary>
        /// Then the entity queried for, and its children will be loaded in one query with Join
        /// Because we added also "DistinctRoot" to query, there will not be root duplicates.
        /// </summary>
        [Test]
        public void When_executing_a_query_which_defines_a_relation_and_which_should_be_Expanded_Eagerly()
        {
            var query = CurrentSession.Linq<Prospect>();

            query.Expand(p => p.Products, FetchMode.Eager)
                 .DistinctRoot()
                 .Where(p => p.Initials == "Initials");

            var list = query.ToList();


            Output("Prospects loaded from db:");
            foreach (var prospect in list)
            {
                Output("Prospect: {0}, {1}", prospect.Id, prospect.Initials);
                Output("Were children initialized? {0}", NHibernateUtil.IsInitialized(prospect.Products));

                foreach (var product in prospect.Products)
                {
                    Output("Product: {0}", product.Id);
                }
            }
        }

        /// <summary>
        /// Then the entity queried for will be loaded, its children will not be loaded until they are called for
        /// </summary>
        [Test]
        public void When_executing_a_query_which_defines_a_relation_and_which_should_be_Expanded_as_Select()
        {
            var query = CurrentSession.Linq<Prospect>();

            query.Expand(p => p.Products, FetchMode.Select)
                //.DistinctRoot()
                 .Where(p => p.Initials == "Initials");

            var list = query.ToList();


            Output("Prospects loaded from db:");
            foreach (var prospect in list)
            {
                Output("Prospect: {0}, {1}", prospect.Id, prospect.Initials);
                Output("Were children initialized? {0}", NHibernateUtil.IsInitialized(prospect.Products));

                foreach (var product in prospect.Products)
                {
                    Output("Product: {0}", product.Id);
                }
            }
        }

        /// <summary>
        /// Then the entity queried for will be loaded, its children will also be loaded, but because of
        /// query being a join, and collections being Bags (allowing duplicates), the children will be duplicated
        /// </summary>
        [Test]
        public void When_executing_a_query_which_defines_three_relations_as_Bags_and_which_should_be_Expanded_Eager()
        {
            var query = CurrentSession.Linq<Prospect>();

            query.Expand(p => p.Products)
                 .Expand(p => p.Orders)
                 .Expand(p => p.Addresses)
                 .DistinctRoot()
                 .Where(p => p.Initials == "Initials");

            var list = query.ToList();


            Output("Prospects loaded from db:");
            foreach (var prospect in list)
            {
                Output("Prospect: {0}, {1}", prospect.Id, prospect.Initials);
                Output("Were products initialized? {0}", NHibernateUtil.IsInitialized(prospect.Products));
                foreach (var product in prospect.Products)
                {
                    Output("Product: {0}", product.Id);
                }

                Output("Were orders initialized? {0}", NHibernateUtil.IsInitialized(prospect.Orders));
                foreach (var order in prospect.Orders)
                {
                    Output("Order: {0}", order.Id);
                }

                Output("Were addresses initialized? {0}", NHibernateUtil.IsInitialized(prospect.Addresses));
                foreach (var address in prospect.Addresses)
                {
                    Output("Address: {0}", address.Id);
                }
            }
        }

        /// <summary>
        /// Then the entity queried for will be loaded, its children will also be loaded, and will be deduplicated.
        /// This is because, although being fetched as a join...they are put in a Set...and set can only contain
        /// unique items.
        /// </summary>
        [Test]
        public void When_executing_a_query_which_defines_three_relations_as_Sets_and_which_should_be_Expanded_Eager()
        {
            var query = CurrentSession.Linq<Prospect>();

            query.Expand(p => p.ProductsSet)
                 .Expand(p => p.OrdersSet)
                 .Expand(p => p.AddressesSet)
                 .DistinctRoot()
                 .Where(p => p.Initials == "Initials");

            var list = query.ToList();


            Output("Prospects loaded from db:");
            foreach (var prospect in list)
            {
                Output("Prospect: {0}, {1}", prospect.Id, prospect.Initials);
                Output("Were products initialized? {0}", NHibernateUtil.IsInitialized(prospect.ProductsSet));
                foreach (var product in prospect.ProductsSet)
                {
                    Output("Product: {0}", product.Id);
                }

                Output("Were orders initialized? {0}", NHibernateUtil.IsInitialized(prospect.OrdersSet));
                foreach (var order in prospect.OrdersSet)
                {
                    Output("Order: {0}", order.Id);
                }

                Output("Were addresses initialized? {0}", NHibernateUtil.IsInitialized(prospect.AddressesSet));
                foreach (var address in prospect.AddressesSet)
                {
                    Output("Address: {0}", address.Id);
                }
            }
        }
    }
}